#region File information

/**
 * File: CollisionHandler.cs
 * Date: 31-03-2008
 * Auth: Gerard v/d Lei
 * 
 * Desc: Interface and class for handling collision between several objects
 */

#endregion

using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Gremlin.Collision
{
    public enum CollisionState {PRE_COLLISION, POST_COLLISION};

    public delegate CollisionObject UpdateCollisionData(CollisionState collisionState, GameTime gameTime);
    public delegate void ImpactResponce(CollisionObject collisionObject, Vector3 point, Vector3 delta);

    /// <summary>
    /// Handles collision between several objects in game world
    /// </summary>
    public class CollisionHandler
    {
        #region Attributes
        private long lastGametime;
        private List<CollisionObject> collisionObjects;
        public event UpdateCollisionData CollisionEvent;
        #endregion

        /// <summary>
        /// Std constructor
        /// </summary>
        public CollisionHandler()
        {
            collisionObjects = new List<CollisionObject>();
        }

        /// <summary>
        /// Performs collision detection responce between all the registered objects
        /// </summary>
        /// <param name="gameTime">Time of current frame</param>
        public void DoCollision(GameTime gameTime)
        {
            //If this is first time this frame, then update objects states (GameTime doesnt work)
            //if (gameTime.TotalRealTime.Milliseconds > lastGametime)
            {
                collisionObjects.Clear();
                foreach (UpdateCollisionData collisionEvent in CollisionEvent.GetInvocationList())
                    collisionObjects.Add(collisionEvent(CollisionState.PRE_COLLISION, gameTime));
                lastGametime = gameTime.TotalRealTime.Milliseconds;
            }

            //Do collision, technically we should repeat this for the object that collides, but this works better now
            int size = collisionObjects.Count;
            for(int i = 0; i < size - 1; i++)
                for (int j = i + 1; j < size; j++)
                {
                    CollisionObject co0 = collisionObjects[i];
                    CollisionObject co1 = collisionObjects[j];
                    if (CollisionObject.FastRangeCheck(co0, co1))
                        CollisionObject.CollisionObjectIntersection(co0, co1);
                }

            //Update post collision data
            CollisionEvent(CollisionState.POST_COLLISION, gameTime);
        }

        /// <summary>
        /// Finds ray intersection for all the registers object against this ray.
        /// Calls the delegate ImpactResponce with the closest hit and that location
        /// as parameters for impact responce, like damage registering or adding 
        /// force impuls.
        /// </summary>
        /// <param name="origin">Origin of ray</param>
        /// <param name="delta">Delta of ray</param>
        /// <param name="gameTime">Time of current frame</param>
        /// <param name="impactResponce">Delegate to call as responce to an impact</param>
        /// <param name="point">Closest intersection point</param>
        /// <returns>True when intersection was found, false otherwise</returns>
        public bool RayIntersection(Vector3 origin, Vector3 delta, GameTime gameTime, ImpactResponce impactResponce, out Vector3 point)
        {
            //If this is first time this frame, then update objects states
            if (gameTime.TotalRealTime.Milliseconds != lastGametime)
            {
                collisionObjects.Clear();
                foreach (UpdateCollisionData collisionEvent in CollisionEvent.GetInvocationList())
                    collisionObjects.Add(collisionEvent(CollisionState.PRE_COLLISION, gameTime));
                lastGametime = gameTime.TotalRealTime.Milliseconds;
            }

            bool hit = false;
            float dist = 0;
            point = new Vector3(0, 0, 0);
            Vector3 closestPoint = new Vector3(0,0,0);
            CollisionObject closestObject = null;
            //Check Ray Intersection against each object and get closest hit
            foreach (CollisionObject co in collisionObjects)
                if (co.RayIntersection(origin, delta, out point))
                {
                    if (!hit)
                    {
                        closestObject = co;
                        closestPoint = point;
                        dist = (origin - point).LengthSquared();
                        hit = true;
                    }else
                    {
                        float d = (origin - point).LengthSquared();
                        if (d < dist)
                        {
                            dist = d;
                            closestPoint = point;
                            closestObject = co;
                        }
                    }
                }
            if (!hit) return false;

            //Hit occoured, responce on closest point
            point = closestPoint;
            if (impactResponce != null)
                impactResponce(closestObject, point, Vector3.Normalize(delta));

            //Update post collision data
            CollisionEvent(CollisionState.POST_COLLISION, gameTime);

            return hit;
        }
    }
}
